package auto.code.rap2.utils;

import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import auto.code.db.utils.RRException;
import auto.code.rap2.entity.ClassTextInfo;
import auto.code.rap2.entity.InterfaceBean;
import auto.code.rap2.entity.Module;
import auto.code.rap2.entity.Properties;
import auto.code.rap2.entity.Result;


import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import freemarker.template.Configuration;

/**
 * Rap2接口生成核心类
 * @author dzc
 */
public class Rap2GenUtil {
	
    private static Configuration configuration = null;

    static {
        configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        configuration.setDefaultEncoding(ConstVal.UTF8);
        configuration.setClassForTemplateLoading(FreemarkerTemplateEngine.class, "/");
    }
	
	public static List<String> getRap2Templates(){
		List<String> templates = new ArrayList<String>();
		templates.add("template/rap2/bean.java.vm");
		return templates;
	}
	
	public static List<String> getRap2ControllerTemplates(){
		List<String> templates = new ArrayList<String>();
		templates.add("template/rap2/controller.java.vm");
		return templates;
	}
	

	public static byte[] singlecode(Module moduleObj) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        GlobalConfig config = new GlobalConfig();
        config.setAuthor(moduleObj.getFullname()+"  "+moduleObj.getEmail()); 
        ConfigBuilder configBuilder = new ConfigBuilder(null, null, null, config);
        
        write(moduleObj,zip, configBuilder, moduleObj.getInterfaces(), moduleObj.getName(), moduleObj.getInterfaces().get(0).getName() , true);
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
	}
	
	public static List<ClassTextInfo> singletext(Module moduleObj) {
		List<ClassTextInfo> javaJson =  writetext(moduleObj, moduleObj.getInterfaces(), moduleObj.getName(), moduleObj.getInterfaces().get(0).getName());
        return javaJson;
	}


	public static byte[] generator(Map<String, String> param) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        GlobalConfig config = new GlobalConfig();
        config.setAuthor(param.get("author"));
        config.setOutputDir("/logs/code");
        ConfigBuilder configBuilder = new ConfigBuilder(null, null, null, config);

        Map<String, String> headMap = new HashMap<>();
        headMap.put("Cookie", param.get("cookie"));

        WebClient.create().get()
                .uri(param.get("address"))
                .header("Cookie", param.get("cookie"))
                .exchange().flatMap(clientResponse -> clientResponse.bodyToMono(Result.class))
                .map(result -> result.getData().getModules()).block()
                .stream().forEach(module -> {
            //单module
            write(module,zip, configBuilder, module.getInterfaces(), module.getName(), param.get("interfaceName"), !StringUtils.equals(param.get("toString"), "1"));
        });
           
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * @param zip
     * @param configBuilder
     * @param interfaces
     * @param moduleName
     * @param interfaceName
     * @param isToString
     */
    private static void write(Module moduleObj,ZipOutputStream zip, ConfigBuilder configBuilder, List<InterfaceBean> interfaces, String moduleName, String interfaceName, boolean isToString) {
        interfaces.stream().filter(i -> {
            //接口名称过滤
            if (StringUtils.isNotBlank(interfaceName) && !StringUtils.equals(i.getName(), interfaceName)) {
                return false;
            }
            return true;
        }).forEach(i -> {
            List<Properties> request = new ArrayList<>();
            List<Properties> response = new ArrayList<>();

            List<Properties> properties = i.getProperties();

            properties.forEach(p -> {
                if (StringUtils.equals(p.getScope(), "request")) {
                    request.add(p);
                }
                if (StringUtils.equals(p.getScope(), "response")) {
                    response.add(p);
                }
            });
            List<Properties> totalProperties = new ArrayList<>();
            List<Properties> requestProperties = request.stream().collect(Collectors.toList());
            List<Properties> responseProperties = response.stream().collect(Collectors.toList());
            if(requestProperties!=null&&requestProperties.size()>0) {
             	totalProperties.addAll(requestProperties);
            }
            if(responseProperties!=null&&responseProperties.size()>0) {
             	totalProperties.addAll(responseProperties);
            }
            
            totalProperties.forEach(p -> {
                if(p.getType().equals("Array") && StringUtils.isBlank(p.getExpandClass())) {
                  	throw new RRException("父类expandClass必须名称，否则不支持导出预览，因为：会导致类名称重复");
                };
            });
            
            totalProperties.forEach(p -> {
                if(p.getType().equals("Function") || p.getType().equals("RegExp")) {
                  	throw new RRException("不支持Function，RegExp类型");
                };
            });
            
            Map<String, List<Properties>> r1 = request.stream().collect(Collectors.groupingBy(Properties::getParentId));
            Map<String, List<Properties>> r2 = response.stream().collect(Collectors.groupingBy(Properties::getParentId));
            request.clear();
            response.clear();
            request.addAll(filter("-1", r1));
            response.addAll(filter("-1", r2));
            r1 = request.stream().collect(Collectors.groupingBy(Properties::getParentId));
            r2 = response.stream().collect(Collectors.groupingBy(Properties::getParentId));
            String name = moduleName + File.separator + i.getName();
            writeBean(totalProperties,r1, "Request", i.getRequestClassName()!=null?i.getRequestClassName():"Request", configBuilder, zip, isToString, name);
            writeBean(totalProperties,r2, "Response", i.getResponseClassName()!=null?i.getResponseClassName():"Response", configBuilder, zip, isToString, name);
            //Controller层场景
            writeController(moduleObj,totalProperties, "Controller", configBuilder, zip,name,i.getRequestClassName()!=null?i.getRequestClassName():"Request",moduleObj.getControllerMethodName(),moduleObj.getControllerPath());
        });
    }
    
    
    /**
     * @param moduleObj
     * @param interfaces
     * @param moduleName
     * @param interfaceName
     * */
    private static List<ClassTextInfo> writetext(Module moduleObj, List<InterfaceBean> interfaces, String moduleName, String interfaceName) {
       List<ClassTextInfo> listClassTextInfo = new ArrayList<>();
    	   interfaces.stream().forEach(i -> {
            List<Properties> request = new ArrayList<>();
            List<Properties> response = new ArrayList<>();

            List<Properties> properties = i.getProperties();

            properties.forEach(p -> {
                if (StringUtils.equals(p.getScope(), "request")) {
                    request.add(p);
                }
                if (StringUtils.equals(p.getScope(), "response")) {
                    response.add(p);
                }
            });
            List<Properties> totalProperties = new ArrayList<>();
            List<Properties> requestProperties = request.stream().collect(Collectors.toList());
            List<Properties> responseProperties = response.stream().collect(Collectors.toList());
            if(requestProperties!=null&&requestProperties.size()>0) {
             	totalProperties.addAll(requestProperties);
            }
            if(responseProperties!=null&&responseProperties.size()>0) {
             	totalProperties.addAll(responseProperties);
            }
            
            totalProperties.forEach(p -> {
                if(p.getType().equals("Array") && StringUtils.isBlank(p.getExpandClass())) {
                  	throw new RRException("父类expandClass必须名称，否则不支持导出预览，因为：会导致类名称重复");
                };
            });
            
            totalProperties.forEach(p -> {
                if(p.getType().equals("Function") || p.getType().equals("RegExp")) {
                  	throw new RRException("不支持Function，RegExp类型");
                };
            });
            
            Map<String, List<Properties>> r1 = request.stream().collect(Collectors.groupingBy(Properties::getParentId));
            Map<String, List<Properties>> r2 = response.stream().collect(Collectors.groupingBy(Properties::getParentId));
            request.clear();
            response.clear();
            request.addAll(filter("-1", r1));
            response.addAll(filter("-1", r2));
            r1 = request.stream().collect(Collectors.groupingBy(Properties::getParentId));
            r2 = response.stream().collect(Collectors.groupingBy(Properties::getParentId));
            
            List<ClassTextInfo> requestJson = writeBeanText(moduleObj,totalProperties,r1, "Request", i.getRequestClassName()!=null?i.getRequestClassName():"Request");
            List<ClassTextInfo> responseJson = writeBeanText(moduleObj,totalProperties,r2, "Response", i.getResponseClassName()!=null?i.getResponseClassName():"Response");
            List<ClassTextInfo> controllerJson = writeControllerText(moduleObj,totalProperties, "Controller",i.getRequestClassName()!=null?i.getRequestClassName():"Request",moduleObj.getControllerMethodName(),moduleObj.getControllerPath());
            
            if(requestJson!=null && requestJson.size()>0) {
              	listClassTextInfo.addAll(requestJson);
            }
            if(responseJson!=null && responseJson.size()>0) {
              	listClassTextInfo.addAll(responseJson);
            }
            if(controllerJson!=null && controllerJson.size()>0) {
              	listClassTextInfo.addAll(controllerJson);
            }
        });
	    return listClassTextInfo;
	}


	/**
     * 从上级开始，循环下级。过滤删除数据
     *
     * @param index
     * @param r2
     * @return
     */
    private static List<Properties> filter(String index, Map<String, List<Properties>> r2) {
        List<Properties> list = r2.get(index);
        List<Properties> nList = new ArrayList<>();

        if (CollectionUtils.isEmpty(list)) {
            return nList;
        }

        ListIterator<Properties> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Properties map = iterator.next();
            nList.add(map);
            if (StringUtils.equals(map.getType(), "Array") || StringUtils.equals(map.getType(), "Object")) {
                nList.addAll(filter(map.getId() + "", r2));
            }
        }
        return nList;
    }

    /**
     * @param data          Properties 数据集合
     * @param className     类名称
     * @param mark          Request Response 标记
     * @param configBuilder
     * @param zip           zip流
     * @param isToString    是否生成toString
     * @param name          文件路径前缀
     */
    private static void writeBean(List<Properties> totalProperties,Map<String, List<Properties>> data, String mark, String className,ConfigBuilder configBuilder, ZipOutputStream zip, boolean isToString, String name) {

        data.forEach((k, v) -> {
            TableInfo tableInfo = buildTableInfo(totalProperties,className, k, v);
            try {     		
                Map<String, Object> map1 = getObjectMap(tableInfo, configBuilder);
                map1.put("toString", isToString);
       
                //获取模板列表
        		    List<String> templates = getRap2Templates();
        		    for (String template : templates) {
	        		    	//渲染模板
	    	    			StringWriter sw = new StringWriter();
	    	    			configuration.getTemplate(template).process(map1, sw);
	    				//添加到zip
	    			    String filename = name + File.separator + mark.toLowerCase() + File.separator + tableInfo.getEntityName() + ".java";
	    				zip.putNextEntry(new ZipEntry(filename));
	    				IOUtils.write(sw.toString(), zip, "UTF-8");
	    				IOUtils.closeQuietly(sw);
	    				zip.closeEntry();
				}
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        });
    }
    
    private static List<ClassTextInfo> writeBeanText(Module moduleObj,List<Properties> totalProperties, Map<String, List<Properties>> data,String mark, String className) {
       List<ClassTextInfo> classTextInfoList = new ArrayList<>();
    	   data.forEach((k, v) -> {
            TableInfo tableInfo = buildTableInfo(totalProperties,className, k, v);
            try {   
                GlobalConfig config = new GlobalConfig();
                config.setAuthor(moduleObj.getFullname()+"  "+moduleObj.getEmail()); 
                ConfigBuilder configBuilder = new ConfigBuilder(null, null, null, config);
                Map<String, Object> map1 = getObjectMap(tableInfo, configBuilder);
              
       
                //获取模板列表
        		    List<String> templates = getRap2Templates();
        		    for (String template : templates) {
	        		    	//渲染模板
        		     	ClassTextInfo classTextInfo = new ClassTextInfo();
	    	    			StringWriter sw = new StringWriter();
	    	    			configuration.getTemplate(template).process(map1, sw);
	    	    			classTextInfo.setClassName(className);
	    	    			classTextInfo.setClassText(sw.toString()); 
	    	    			classTextInfo.setType(mark);
	    	    			classTextInfoList.add(classTextInfo);
				}
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        });
		return classTextInfoList;
	}
    
    
    /**
     * @param data          Properties 数据集合
     * @param mark          Request Response 标记
     * @param configBuilder
     * @param zip           zip流
     * @param isToString    是否生成toString
     * @param name          文件路径前缀
     * @param className     方法类入参
     * @param methodName    方法名称
     * @param reuestPath    访问路径
     */
    @SuppressWarnings("unused")
	private static void writeController(Module moduleObj,List<Properties> totalProperties, String mark, ConfigBuilder configBuilder, ZipOutputStream zip, String name,String className,String methodName,String reuestPath) {
     	Map<String, Object> map1 = new HashMap<>();
    	    //参数打散做入参数
        List<Properties>  branchParam = totalProperties.stream().filter(x->x.getParentId().equals("-1") && x.getScope().equals("request")).collect(Collectors.toList());
        map1.put("branchParam", branchParam);
        map1.put("controllerClassName", className);
        map1.put("objectClassName", lowerFirstCapse(className));
        map1.put("controllerMethodName", methodName);
        map1.put("controllerPath", reuestPath);
        map1.put("author", moduleObj.getFullname()+"  "+moduleObj.getEmail());
        map1.put("date", (new SimpleDateFormat("yyyy-MM-dd")).format(new Date()));
        
	    try {     		
	           //获取模板列表
			    List<String> templates = getRap2ControllerTemplates();
			    for (String template : templates) {
	  		    	//渲染模板
		    			StringWriter sw = new StringWriter();
		    			configuration.getTemplate(template).process(map1, sw);
					//添加到zip
				    String filename = name + File.separator + mark.toLowerCase() + File.separator + mark + ".java";
					zip.putNextEntry(new ZipEntry(filename));
					IOUtils.write(sw.toString(), zip, "UTF-8");
					IOUtils.closeQuietly(sw);
					zip.closeEntry();
			}
	    } catch (Exception e1) {
	        e1.printStackTrace();
	    }
    }
    
    
    /**
     * @param data          Properties 数据集合
     * @param mark          Request Response 标记
     * @param configBuilder
     * @param zip           zip流
     * @param isToString    是否生成toString
     * @param name          文件路径前缀
     * @param className     方法类入参
     * @param methodName    方法名称
     * @param reuestPath    访问路径
     * @return 
     */
	private static List<ClassTextInfo> writeControllerText(Module moduleObj,List<Properties> totalProperties, String mark,String className,String methodName,String reuestPath) {
     	List<ClassTextInfo> classTextInfoList = new ArrayList<>();
     	Map<String, Object> map1 = new HashMap<>();
    	    //参数打散做入参数
        List<Properties>  branchParam = totalProperties.stream().filter(x->x.getParentId().equals("-1") && x.getScope().equals("request")).collect(Collectors.toList());
        map1.put("branchParam", branchParam);
        map1.put("controllerClassName", className);
        map1.put("objectClassName", lowerFirstCapse(className));
        map1.put("controllerMethodName", methodName);
        map1.put("controllerPath", reuestPath);
        map1.put("author", moduleObj.getFullname()+"  "+moduleObj.getEmail());
        map1.put("date", (new SimpleDateFormat("yyyy-MM-dd")).format(new Date()));
        
	    try {     		
	           //获取模板列表
			    List<String> templates = getRap2ControllerTemplates();
			    for (String template : templates) {
	  		    	//渲染模板
		    			StringWriter sw = new StringWriter();
		    			configuration.getTemplate(template).process(map1, sw);
		    			ClassTextInfo classTextInfo = new ClassTextInfo();
		    			classTextInfo.setClassName(className);
	    	    			classTextInfo.setClassText(sw.toString()); 
	    	    			classTextInfo.setType(mark);
	    	    			classTextInfoList.add(classTextInfo);
			}
	    } catch (Exception e1) {
	        e1.printStackTrace();
	    }
		return classTextInfoList;
    }


    /**
     * 用数据库逆向工程方式来构造数据生成
     *
     * @param mark 标记 request response
     * @param k    id
     * @param v
     * @return
     */
    private static TableInfo buildTableInfo(List<Properties> totalProperties,String mark, String k, List<Properties> v) {
        TableInfo tableInfo = new TableInfo();
        List<TableField> fields = new ArrayList<>();

        v.forEach(l -> {
            String type = l.getType() + "";
            TableField tableField = new TableField();

            IColumnType columnType = null;
            Map<String, Object> customMap = null;
            if (StringUtils.equals(type, "String")) {
                columnType = DbColumnTypeEx.STRING;
            } else if (StringUtils.equals(type, "Boolean")) {
                columnType = DbColumnTypeEx.BOOLEAN;
            } else if (StringUtils.equals(type, "Number")) {
                columnType = DbColumnTypeEx.INTEGER;
            } else if (StringUtils.equals(type, "Array")) {
                columnType = DbColumnTypeEx.ARRAY_LIST;
                customMap = new HashMap<>();
                //对象名称
                String defaulTypeName = mark + l.getId();
                defaulTypeName = totalProperties.stream().filter(x->x.getId().intValue()==Integer.valueOf(l.getId()).intValue()).map(x->x.getExpandClass()).findAny().orElse(defaulTypeName);
                customMap.put("type", defaulTypeName);
            } else {
                columnType = DbColumnTypeEx.OBJECT;
                customMap = new HashMap<>();
                //对象名称
                String defaulTypeName = mark + l.getId();
                defaulTypeName = totalProperties.stream().filter(x->x.getId().intValue()==Integer.valueOf(l.getId()).intValue()).map(x->x.getExpandClass()).findAny().orElse(defaulTypeName);
                customMap.put("type", defaulTypeName);
            }
            if (MapUtils.isNotEmpty(customMap)) {
                tableField.setCustomMap(customMap);
            }
            tableField.setColumnType(columnType);
            tableField.setPropertyName(l.getName() + "");
            tableField.setComment(l.getDescription() + "");
            fields.add(tableField);
        });
        
        //类命名
        String defaultEntityName = mark;
        if(!StringUtils.equals(k, "-1")){
          	defaultEntityName = totalProperties.stream().filter(x->x.getId().intValue()==Integer.valueOf(k).intValue()).map(x->x.getExpandClass()).findAny().orElse(defaultEntityName);
        }
        tableInfo.setFields(fields);
        tableInfo.setEntityName(defaultEntityName);
        return tableInfo;
    }

    /**
     * 用数据库逆向工程方式来构造数据生成
     * @param tableInfo
     * @param config
     * @return
     */
    public static Map<String, Object> getObjectMap(TableInfo tableInfo, ConfigBuilder config) {
        Map<String, Object> objectMap = new HashMap(30);
        if (config.getStrategyConfig().isControllerMappingHyphenStyle()) {
            objectMap.put("controllerMappingHyphenStyle", config.getStrategyConfig().isControllerMappingHyphenStyle());
            objectMap.put("controllerMappingHyphen", com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToHyphen(tableInfo.getEntityPath()));
        }

        objectMap.put("restControllerStyle", config.getStrategyConfig().isRestControllerStyle());
        objectMap.put("config", config);
        objectMap.put("package", config.getPackageInfo());
        GlobalConfig globalConfig = config.getGlobalConfig();
        objectMap.put("author", globalConfig.getAuthor());
        objectMap.put("idType", globalConfig.getIdType() == null ? null : globalConfig.getIdType().toString());
        objectMap.put("logicDeleteFieldName", config.getStrategyConfig().getLogicDeleteFieldName());
        objectMap.put("versionFieldName", config.getStrategyConfig().getVersionFieldName());
        objectMap.put("activeRecord", globalConfig.isActiveRecord());
        objectMap.put("kotlin", globalConfig.isKotlin());
        objectMap.put("swagger2", globalConfig.isSwagger2());
        objectMap.put("date", (new SimpleDateFormat("yyyy-MM-dd")).format(new Date()));
        objectMap.put("table", tableInfo);
        objectMap.put("enableCache", globalConfig.isEnableCache());
        objectMap.put("baseResultMap", globalConfig.isBaseResultMap());
        objectMap.put("baseColumnList", globalConfig.isBaseColumnList());
        objectMap.put("entity", tableInfo.getEntityName());
        objectMap.put("entityColumnConstant", config.getStrategyConfig().isEntityColumnConstant());
        objectMap.put("entityBuilderModel", config.getStrategyConfig().isEntityBuilderModel());
        objectMap.put("entityLombokModel", config.getStrategyConfig().isEntityLombokModel());
        objectMap.put("entityBooleanColumnRemoveIsPrefix", config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix());
        objectMap.put("superMapperClassPackage", config.getSuperMapperClass());
        objectMap.put("superServiceClassPackage", config.getSuperServiceClass());
        objectMap.put("superServiceImplClassPackage", config.getSuperServiceImplClass());
        objectMap.put("superControllerClassPackage", config.getSuperControllerClass());
        return objectMap;
    }
    
    public static String lowerFirstCapse(String str){
      	char[]chars = str.toCharArray();
      	chars[0] += 32;
      	return String.valueOf(chars);
    	}
}
